package spine.starling
{
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import spine.Bone;
	import spine.Skeleton;
	import spine.SkeletonData;
	import spine.Slot;
	import spine.attachments.RegionAttachment;
	
	import starling.animation.IAnimatable;
	import starling.core.RenderSupport;
	import starling.display.DisplayObject;
	import starling.display.Image;
	import starling.textures.RenderTexture;
	import starling.utils.MatrixUtil;

	public class SkeletonSprite extends DisplayObject implements IAnimatable
	{

		private var mClipRect:Rectangle;

		static private var tempPoint:Point=new Point();
		static private var tempMatrix:Matrix=new Matrix();
		
		/** Helper objects. */
		private static var sHelperMatrix:Matrix = new Matrix();
		private static var sHelperPoint:Point = new Point();
		private static var sHelperRect:Rectangle = new Rectangle();

		private var _skeleton:Skeleton;

		private var vertices:Vector.<Number>=new Vector.<Number>();

		private var localbounds:Rectangle=new Rectangle();

		private var _openReflex:Boolean=false;

		private var snapshotCache:Vector.<Image>;

		public function SkeletonSprite(skeletonData:SkeletonData, openReflex:Boolean=false)
		{
			Bone.yDown=true;

			_skeleton=new Skeleton(skeletonData);
			_skeleton.updateWorldTransform();

			vertices.length=8;

			updateBounds();

			_openReflex=openReflex;

			if (_openReflex)
				initSnapshot();
		}

		private function initSnapshot():void
		{
			snapshotCache=new Vector.<Image>();
		}

		public function advanceTime(delta:Number):void
		{
			_skeleton.update(delta);
			updateBounds();
		}

		private var updating:Boolean=false;
		
		public function get clipRect():Rectangle { return mClipRect; }
		public function set clipRect(value:Rectangle):void 
		{
			if (mClipRect && value) mClipRect.copyFrom(value);
			else mClipRect = (value ? value.clone() : null);
		}
		
		/** Returns the bounds of the container's clipRect in the given coordinate space, or
		 *  null if the sprite doens't have a clipRect. */ 
		public function getClipRect(targetSpace:DisplayObject, resultRect:Rectangle=null):Rectangle
		{
			if (mClipRect == null) return null;
			if (resultRect == null) resultRect = new Rectangle();
			
			var x:Number, y:Number;
			var minX:Number =  Number.MAX_VALUE;
			var maxX:Number = -Number.MAX_VALUE;
			var minY:Number =  Number.MAX_VALUE;
			var maxY:Number = -Number.MAX_VALUE;
			var transMatrix:Matrix = getTransformationMatrix(targetSpace, sHelperMatrix);
			
			for (var i:int=0; i<4; ++i)
			{
				switch(i)
				{
					case 0: x = mClipRect.left;  y = mClipRect.top;    break;
					case 1: x = mClipRect.left;  y = mClipRect.bottom; break;
					case 2: x = mClipRect.right; y = mClipRect.top;    break;
					case 3: x = mClipRect.right; y = mClipRect.bottom; break;
				}
				var transformedPoint:Point = MatrixUtil.transformCoords(transMatrix, x, y, sHelperPoint);
				
				if (minX > transformedPoint.x) minX = transformedPoint.x;
				if (maxX < transformedPoint.x) maxX = transformedPoint.x;
				if (minY > transformedPoint.y) minY = transformedPoint.y;
				if (maxY < transformedPoint.y) maxY = transformedPoint.y;
			}
			
			resultRect.setTo(minX, minY, maxX-minX, maxY-minY);
			return resultRect;
		} 

		override public function render(support:RenderSupport, alpha:Number):void
		{
			
			if (mClipRect)
			{
				var clipRect:Rectangle = support.pushClipRect(getClipRect(stage, sHelperRect));
				if (clipRect.isEmpty())
				{
					// empty clipping bounds - no need to render children.
					support.popClipRect();
					return;
				}
			}
			
			var drawOrder:Vector.<Slot>=skeleton.drawOrder;

			if (_openReflex)
				disposeSnapshotCache();

			for (var i:int=0, n:int=drawOrder.length; i < n; i++)
			{
				var slot:Slot=drawOrder[i];
				var regionAttachment:RegionAttachment=slot.attachment as RegionAttachment;
				if (regionAttachment != null)
				{
					var vertices:Vector.<Number>=this.vertices;
					regionAttachment.computeVertices(skeleton.x, skeleton.y, slot.bone, vertices);
					var r:Number=skeleton.r * slot.r;
					var g:Number=skeleton.g * slot.g;
					var b:Number=skeleton.b * slot.b;
					var a:Number=skeleton.a * slot.a;

					var image:SkeletonImage=regionAttachment.rendererObject as SkeletonImage;
					var vertexData:Vector.<Number>=image.vertexData.rawData;

					vertexData[0]=vertices[2];
					vertexData[1]=vertices[3];
					vertexData[2]=r;
					vertexData[3]=g;
					vertexData[4]=b;
					vertexData[5]=a;

					vertexData[8]=vertices[4];
					vertexData[9]=vertices[5];
					vertexData[10]=r;
					vertexData[11]=g;
					vertexData[12]=b;
					vertexData[13]=a;

					vertexData[16]=vertices[0];
					vertexData[17]=vertices[1];
					vertexData[18]=r;
					vertexData[19]=g;
					vertexData[20]=b;
					vertexData[21]=a;

					vertexData[24]=vertices[6];
					vertexData[25]=vertices[7];
					vertexData[26]=r;
					vertexData[27]=g;
					vertexData[28]=b;
					vertexData[29]=a;

					image.updateVertices();
					
					if (_openReflex)
						snapshotCache.push(image);

					support.batchQuad(image, alpha, image.texture);
				}
			}
			
			if (_openReflex)
				updateSnapshot();
			if (mClipRect)
				support.popClipRect();
		}


		/**
		 * 更新倒影位置
		 * @param reflexModelParent 倒影模型父容器(snapShotContainer's father)
		 */
		/*	public function updateReflex(reflexModelParent:Sprite):void
			{
				try
				{
					_showRelfex=true;
					shadowModel.scaleX=this.scaleX;
					shadowModel.x=this.x-reflexModelParent.x;
					shadowModel.y=this.y-reflexModelParent.y;
					_renderTexure.draw(shadowModel);
				}
				catch(e:Error)
				{
					trace("there is some thing wrong with the  variable shadowModel");
				}
			}*/


		private var _renderTexure:RenderTexture=new RenderTexture(width, height, false);

		public function get renderTexure():RenderTexture
		{
			return _renderTexure;
		}

		public function set renderTexure(value:RenderTexture):void
		{
			_renderTexure=value;
		}

		private var _shadowModel:PuppetModel;

		private function updateSnapshot():void
		{
			try
			{
				if (_shadowModel)
				{
					_shadowModel.clear();
					for (var i:int=0; i < snapshotCache.length; i++)
					{
						_shadowModel.addElement(snapshotCache[i]);
					}
				}
			}
			catch (e:Error)
			{
				trace("shadowModel is null,please initialize this variable");
			}
		}
		
		public function disposeShadowModel():void
		{
			if(_shadowModel)
			{
				trace("dispose");
				_shadowModel.clear();
				_shadowModel.removeFromParent();
				_shadowModel=null;
				disposeSnapshotCache();
			}
		}
		
		private function disposeSnapshotCache():void
		{
			if (!snapshotCache)
				snapshotCache=new Vector.<Image>();
			if (snapshotCache.length != 0)
			{
				for (var j:int=0; j < snapshotCache.length; j++)
				{
					snapshotCache[j].dispose();
					snapshotCache[j].texture.dispose();
				}
				snapshotCache.length=0;
			}
		}

		public function get shadowModel():PuppetModel
		{
			return _shadowModel;
		}

		public function set shadowModel(value:PuppetModel):void
		{
			_shadowModel=value;
		}

		/**
		 * 是否开启反射倒影效果,可运行时关闭
		 */
		public function get openReflex():Boolean
		{
			return _openReflex;
		}

		/**
		 * @private
		 */
		public function set openReflex(value:Boolean):void
		{
			_openReflex=value;
		}


		override public function hitTest(localPoint:Point, forTouch:Boolean=false):DisplayObject
		{
			if (forTouch && (!visible || !touchable))
				return null;

			if (localbounds.containsPoint(localPoint))
				return this;

			return null;
		}

		override public function getBounds(targetSpace:DisplayObject, resultRect:Rectangle=null):Rectangle
		{
			if (!resultRect)
				resultRect=new Rectangle();
			if (targetSpace == this)
				resultRect.copyFrom(localbounds);
			else if (targetSpace == parent)
				resultRect.setTo(x, y, 0, 0);
			else
			{
				getTransformationMatrix(targetSpace, tempMatrix);
				MatrixUtil.transformCoords(tempMatrix, 0, 0, tempPoint);
				resultRect.setTo(tempPoint.x + localbounds.x, tempPoint.y + localbounds.y, localbounds.width, localbounds.height);
			}
			return resultRect;
		}

		public function get skeleton():Skeleton
		{
			return _skeleton;
		}

		private function updateBounds():void
		{
			var minX:Number=Number.MAX_VALUE, minY:Number=Number.MAX_VALUE;
			var maxX:Number=Number.MIN_VALUE, maxY:Number=Number.MIN_VALUE;
			var slots:Vector.<Slot>=skeleton.slots;
			var value:Number;
			for (var i:int=0, n:int=slots.length; i < n; i++)
			{
				var slot:Slot=slots[i];
				var regionAttachment:RegionAttachment=slot.attachment as RegionAttachment;
				if (!regionAttachment)
					continue;

				var vertices:Vector.<Number>=this.vertices;
				regionAttachment.computeVertices(skeleton.x, skeleton.y, slot.bone, vertices);

				value=vertices[0];
				if (value < minX)
					minX=value;
				if (value > maxX)
					maxX=value;

				value=vertices[1];
				if (value < minY)
					minY=value;
				if (value > maxY)
					maxY=value;

				value=vertices[2];
				if (value < minX)
					minX=value;
				if (value > maxX)
					maxX=value;

				value=vertices[3];
				if (value < minY)
					minY=value;
				if (value > maxY)
					maxY=value;

				value=vertices[4];
				if (value < minX)
					minX=value;
				if (value > maxX)
					maxX=value;

				value=vertices[5];
				if (value < minY)
					minY=value;
				if (value > maxY)
					maxY=value;

				value=vertices[6];
				if (value < minX)
					minX=value;
				if (value > maxX)
					maxX=value;

				value=vertices[7];
				if (value < minY)
					minY=value;
				if (value > maxY)
					maxY=value;
			}

			minX*=scaleX;
			maxX*=scaleX;
			minY*=scaleY;
			maxY*=scaleY;
			var temp:Number;
			if (maxX < minX)
			{
				temp=maxX;
				maxX=minX;
				minX=temp;
			}
			if (maxY < minY)
			{
				temp=maxY;
				maxY=minY;
				minY=temp;
			}

			localbounds.setTo(minX, minY, maxX - minX, maxY - minY);
		}
	}

}

